# 可以自己import我们平台支持的第三方python模块，比如pandas、numpy等。
# 为了不报错引入的模块
import logging as logger
from util.util import *
from util.finance import *
import util.scheduler as scheduler
# 机器学习使用 这里才开始拷贝
from sklearn.preprocessing import StandardScaler
from scipy.stats.mstats import winsorize
from sklearn.linear_model import LinearRegression, Ridge

# numpy ,panda
import pandas as pd
import numpy as np


#
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    # 需要上证所有的股票  
    # 获取所有股票代码列表
    stocks = all_instruments('CS')
    context.all_stock = stocks['order_book_id'].tolist()
    len(context.all_stock)
    # 初始参数
    # 在total_days天内完成上涨和下跌的全过程
    context.total_days = 16
    # 距今天数：表示高点的到今天的交易日数，从高点数起，到今天总的K线数目，其中包括高点K线，包括今日K线
    context.max_down_days = 5
    # {上涨天数：表示高点前"上涨天数"个工作日内有一段"上涨幅度"以上的涨幅}
    context.max_up_days = 10
    # {交易下跌幅度}
    context.fail_rate_to_buy = 0.185
    # {监控下跌幅度}
    context.minitor_fail_rate = 0.1
    # {满足“完美涨幅”的股票，不需要三个涨停也特别好}
    context.rocket_rate = 1.47
    # 最大涨幅不能超过2.2
    context.rocket_max_rate = 2.2

    # 反弹幅度:=0.115;{反弹幅度：表示反弹幅度，高点下跌过程中，已经有过"反弹幅度"大小的反弹，就舍弃，如果创新低的话，可以重新考虑，因为有的首战不仅仅只做一次}
    context.rebound_rate = 1.11
    # 上限一字涨停数:=1.00;{上线涨停数：表示在"高点前几天"时间段中，涨停的个数上限}
    context.word_one_up = 1

    pass


def days_up_rate(order_book_id, days):
    """
    过去n天的最大涨幅,比如,1涨到2,为涨幅1,不是涨幅2
    :param order_book_id: order_book_id	str	合约代码，必填项
    :param days: 往回看的总天数,1表示回看一天,也就是昨天
    :return: 
    """
    # 获取到了最低值,和对应的index 
    # history_bars(stock, days, '1d', 'low') 为 <class 'numpy.ndarray'>
    stock_price = history_bars(order_book_id, days, '1d', ['open', 'low', 'high', 'close'])
    # stock_price = np.array(
    #     [(4.44, 4.11, 4.45, 4.13), (4.2, 4.08, 4.26, 4.09),
    #      (4.06, 4.03, 4.3, 4.21), (4.18, 4.16, 4.49, 4.42),
    #      (4.41, 4.39, 4.86, 4.86), (5.35, 5.35, 5.35, 5.35),
    #      (5.89, 5.89, 5.89, 5.89), (6.3, 6.02, 6.48, 6.48),
    #      (6.14, 6.14, 7.12, 6.87), (6.76, 6.54, 7.56, 7.56),
    #      (7.86, 7.18, 8.32, 8.32), (9.15, 8.75, 9.15, 9.15),
    #      (9.7, 8.24, 10., 8.78), (8.3, 7.9, 8.64, 8.2),
    #      (8.1, 7.71, 8.49, 8.25), (7.79, 7.43, 7.96, 7.51),
    #      (7.27, 7.15, 7.84, 7.4), (7.21, 7.01, 7.45, 7.3),
    #      (7.3, 6.97, 7.52, 6.98), (6.95, 6.9, 7.23, 7.11)])

    # 找到最低点和最低点的index
    lowest = np.min(stock_price, axis=0)[1]

    lowest_index = np.argmin(stock_price, axis=0)[1]

    logger.info("%s 天内最低点 %.2f" % (days, lowest))
    logger.info("%s 天内最低点的index %d" % (days, lowest_index))

    # 最低点到现在的天数
    lowest_to_now_days = days - lowest_index - 1

    # 当天最低点出现
    if lowest_to_now_days == 0:
        logger.info("当天是最低点,取出收盘价作为上涨的价格")
        # 为啥不用最高价,因为最高价可以在最低价前面出现,不一定是反弹的
        lowest_day_close = stock_price[:, 3:4][lowest_index]
        return lowest_day_close / lowest - 1

    stock_price = stock_price[lowest_index:]
    # lowest_index = 0

    high_list = stock_price[:, 2:3]
    highest = np.max(high_list)

    # 切片后,最高点的位置
    # highest_index = np.argmax(stock_price[:, 2:3])

    # 这个方法求最高点的位置
    # np.argwhere(stock_price[:,2:3]==highest)[0][0]

    return (highest / lowest) - 1


# 最低点到昨天的多少天,最低点为第一天,也就是昨天可能为第一天
def lowest_to_yesterday_days(stock, days):
    # 获取到了最低值,和对应的index 
    stock_low_list = history_bars(stock, days, '1d', 'low').tolist()
    # 找到最低点和最低点的index
    lowest = min(stock_low_list)
    lowest_index = stock_low_list.index(lowest)
    return days - lowest_index


# 最高点到昨天的多少天,最高点为第一天,也就是昨天可能为第一天
def highest_to_yesterday_days(stock, days):
    # 其他天最低点
    stock_high_list = history_bars(stock, days, '1d', 'high').tolist()
    # 获取到了最高值和index
    highest = max(stock_high_list)
    highest_index = stock_high_list.index(highest)
    return days - highest_index


# 一字涨停数量
def has_line_price_count(days, stock, highest_to_yesterday_period):
    line_price_counts = 0
    # 最低点到昨天的多少天,最低点为第一天,也就是昨天可能为第一天
    lowest_to_yesterday_period = lowest_to_yesterday_days(stock, days)
    # 获取上升区间的股票每日最高和最低
    high_list = history_bars(stock, lowest_to_yesterday_period, '1d', 'high').tolist()
    low_list = history_bars(stock, lowest_to_yesterday_period, '1d', 'low').tolist()
    close_list = history_bars(stock, lowest_to_yesterday_period, '1d', 'close').tolist()
    # <class 'numpy.ndarray' 需要 high==low 并且 low>前一天close*1.09
    up_days = lowest_to_yesterday_period - highest_to_yesterday_period
    for i in range(1, up_days + 1):
        # 一字涨停,虽然不严谨,但是够了
        if high_list[i] == low_list[i] and (low_list[i] > close_list[i - 1] * 1.09):
            line_price_counts = line_price_counts + 1
    return line_price_counts


# 最高点是多少
def highest_in_days(stock, days):
    # 其他天最低点
    stock_high_list = history_bars(stock, days, '1d', 'high').tolist()
    # 获取到了最高值和index
    highest = max(stock_high_list)
    return highest


#  有三种情况：一是复牌，二是上市，三是股价低于1元，都不做
# 排除长期停牌后复牌一个巨大的跳空高开，一天涨幅0.11，
def check_up_rate_ok(days, stock, highest_to_yesterday_period):
    # 上市天数<20的股票,波动太大,不做
    if instruments(stock).days_from_listed() <= 20:
        return False
    is_up_rate_ok = True
    # 最低点到昨天的多少天,最低点为第一天,也就是昨天可能为第一天
    lowest_to_yesterday_period = lowest_to_yesterday_days(stock, days)

    # 获取上升区间的股票每日最高和最低
    high_list = history_bars(stock, lowest_to_yesterday_period, '1d', 'high').tolist()
    close_list = history_bars(stock, lowest_to_yesterday_period, '1d', 'close').tolist()

    #  需要 high >=前一天close*1.11 ,排除长期停牌后复牌一个巨大的跳空高开，一天涨幅0.11
    up_days = lowest_to_yesterday_period - highest_to_yesterday_period
    for i in range(0, up_days + 1):
        # 上涨幅度是不是超过0.11,超过的股票都不值得做
        if (high_list[i] >= close_list[i - 1] * 1.11):
            is_up_rate_ok = False
            break
    return is_up_rate_ok


def up_ok(context, stock):
    up_total_days_rate = days_up_rate(order_book_id=stock, days=context.total_days)
    # 获取上涨幅度 过滤掉90%的股票,1.37不需要改
    if up_total_days_rate < 1.37:
        # 涨幅不够,这个只是初筛,可以让很多先淘汰出去

        logger.info("股票: %s 上涨幅度不够: %.2f" % (stock, up_total_days_rate))
        return False

    # 高点到昨天的天数 ,最高点为第一天,往下下跌5天,第五天为昨天,没戏了
    # 过滤掉5%的股票  
    highest_to_yesterday_period = highest_to_yesterday_days(stock, context.total_days)
    is_down_short_period = highest_to_yesterday_period <= context.max_down_days
    if not is_down_short_period:
        logger.info("股票: %s 下跌时间短?: %s" % (stock, is_down_short_period))
        return False

    # 30天内,最低点涨幅超过2.2,不能做,风险太大, 干脆不做了,因为太不确定了.  去掉0.1% 股票
    up_30_days_rate = days_up_rate(order_book_id=stock, days=30)
    # 参数2.2可以调整,但是不知道调整幅度是多少
    if up_30_days_rate >= context.rocket_max_rate:
        logger.info("股票: %s 上涨30天比率: %.2f" % (stock, up_30_days_rate))
        return False

    # 一字涨停>=2的去掉,此参数无需调整
    line_price_counts = has_line_price_count(context.total_days, stock, highest_to_yesterday_period)
    if line_price_counts >= 2:
        logger.info("股票: %s 一字涨停数量: %d" % (stock, line_price_counts))
        return False

    #  有三种情况：一是复牌，二是上市，三是股价低于1元，计算涨停板价采用四舍五入极小可能出现这种情况。}
    #  排除长期停牌后复牌一个巨大的跳空高开，一天涨幅0.11
    is_all_days_up_rate_ok = check_up_rate_ok(context.total_days, stock, highest_to_yesterday_period)
    if not is_all_days_up_rate_ok:
        # 上涨不正常,不操作
        logger.info("股票: %s 每天涨幅都小于0.11: %s" % (stock, is_all_days_up_rate_ok))

        return False

    # 上涨 的方法,相互是或关系
    up_condition = False
    logger.info("股票: %s 所有天数总的涨幅: %.2f 一字涨停数量 %d" % (stock, up_total_days_rate, line_price_counts))
    if up_total_days_rate > context.rocket_rate:
        # 上涨幅度有三种情况 1.47,而且一字涨停要去掉
        # 一字涨停要去掉
        if line_price_counts == 1:
            if (up_total_days_rate / 1.1) > context.rocket_rate:
                up_condition = True
            else:
                up_condition = False
                pass
            pass
        else:
            up_condition = True
    else:
        # 三个涨停高开往上, 如果一字涨停就不去做了
        if line_price_counts == 1:
            up_condition = False
        else:
            # todo 三个涨停高开往上, 
            up_condition = True

    return up_condition


# 过去的n天的下跌幅度,需要0.11
def monitor_down_ok(context, stock):
    # 最高点到昨天的天数
    highest_to_yesterday_period = highest_to_yesterday_days(stock, context.total_days)

    # 下跌的最多到昨天是5天,这个时间点看看,最低点和最高点有没有下跌0.11了
    stock_down_list = history_bars(stock, highest_to_yesterday_period, '1d', 'low').tolist()
    # 找到最低点和最低点的index
    down_least = min(stock_down_list)
    down_least_index = stock_down_list.index(down_least)

    # 最低点到昨天超过3天,就从最低点为第一天开始数,到昨天为第三天,就不要监控
    if context.max_down_days - down_least_index >= 3:
        return False

    # 下跌幅度计算,其中,最高点是在最低点前面,不能获取到反弹最高点
    highest = highest_in_days(stock, context.total_days)

    # 下跌幅度超过0.11,放到告警监控中去
    down_rate = 1 - (down_least / highest)
    down_condition = down_rate > context.minitor_fail_rate
    # 判断下跌满足要求
    return down_condition


# before_trading此函数会在每天交易开始前被调用，当天只会被调用一次
# bar_dict这个是None
def before_trading(context, bar_dict):
    monitor_list = []
    for stock in context.all_stock:
        close = history_bars(stock, 1, '1d', 'close')
        # 未上市,或者退市的股票size 为0
        if close.size == 0:
            logger.info("股票: %s 未上市交易 " % (stock))
            continue

            # 不是st股票(包括st,*st,sst,s*st等)
        stock_is_st = is_st_stock(stock)
        if stock_is_st:
            logger.info("股票: %s 停牌: %s" % (stock, stock_is_st))
            continue

        # 排除股价不足2元的垃圾股
        low = history_bars(stock, 1, '1d', 'low')[0]
        if low <= 2:
            logger.info("股票: %s 金额过小为: %d" % (stock, low))
            continue

        #  {排除停牌情况} ,未上市的排除不了  
        if is_suspended(stock, count=1):
            logger.info("股票停牌 %s" % stock)
            continue

        # 上涨是否满足条件,定义一个方法 
        up_condition = up_ok(context=context, stock=stock)

        if not up_condition:
            # 上涨不满足要求,就不用看了
            logger.info("股票: %s 涨幅满足条件?: %s" % (stock, up_condition))
            continue

        # 跌幅也要达到监控要求,不是购买要求,是监控要求,也就是0.11就可以了
        # 开始下跌到昨天的天数
        down_condition = monitor_down_ok(context=context, stock=stock)

        if not down_condition:
            # 下跌不满足要求,就不用看了
            logger.info("股票: %s 下跌满足条件?: %s" % (stock, down_condition))
            continue

        # 上涨和下跌都满足要求的就可以放到监控中去了
        logger.info("股票: %s 都满足条件?: %s" % (stock, True))
        monitor_list.append(stock)
    logger.info(monitor_list)
    context.monitor_list = monitor_list
    pass


# 你选择的证券的数据更新将会触发此段逻辑，例如日或分钟历史数据切片或者是实时数据切片更新

# bar_dict[order_book_id] 可以拿到某个证券的bar信息
# context.portfolio 可以拿到现在的投资组合状态信息
# 使用order_shares(id_or_ins, amount)方法进行落单
# bar_dict 为 BarMap() 
def handle_bar(context, bar_dict):
    for stock in context.monitor_list:
        logger.info("股票: %s  " % (stock))
        # 停牌不做
        if bar_dict[stock].suspended:
            continue
        # 最低价
        low = bar_dict[stock].low
        # 最高点
        highest = highest_in_days(stock, context.total_days)
        # 跌幅要够,这个淘汰率 90%
        # 下跌需要小于高点0.815 
        if (low / highest) > context.fail_rate_to_buy:
            continue

        # 今天high价
        high = bar_dict[stock].high
        # 昨天close价
        yesterday_close = history_bars(stock, 1, '1d', 'close')[0]

        #  {排除下跌时候一字跌停情况} 
        if high == low and high < yesterday_close * 0.91:
            continue

        #  反弹幅度 不超过1.11
        is_up_little = re_up_rate(context, stock)
        if not is_up_little:
            # 反弹幅度过大,不可以使用
            continue
        # 这个可以购买股票了,持仓40%吧

    pass


# 过去的n天的反弹,需要1.11
def re_up_rate(context, stock):
    # 最高点到昨天的天数
    highest_to_yesterday_period = highest_to_yesterday_days(stock, context.total_days)

    # 下跌的最多到昨天是5天
    stock_down_list = history_bars(stock, highest_to_yesterday_period, '1d', 'low').tolist()

    # 找到最低点和最低点的index
    down_least = min(stock_down_list)
    down_least_index = stock_down_list.index(down_least)

    # 反弹天数,这个是以昨天为第一天,总共反弹了多少天,取值>=1
    re_up_days = highest_to_yesterday_period - down_least_index

    # 高点到昨天的天数         
    re_up_high_list = history_bars(stock, re_up_days, '1d', 'high').tolist()
    # 获取到了最高值和index
    re_up_highest = max(re_up_high_list)
    re_up_highest_index = re_up_high_list.index(re_up_highest)

    # 反弹幅度小,todo 这个后面要好好想想,昨天反弹的情况要怎么处理
    is_up_little = True
    # 如果最高点在最低点同一天
    if re_up_highest_index == 0:
        # 下跌就没有问题,如果上涨了就要判断是否
        stock_close = history_bars(stock, re_up_days, '1d', 'close')[0]
        stock_open = history_bars(stock, re_up_days, '1d', 'open')[0]
        if stock_close <= stock_open:
            # 说明是下跌的,反弹不算最低点那天的最高值
            if re_up_days == 1:
                # 说明是昨天最低点收了阴线,这样意味着就没有反弹
                is_up_little = True
                pass
            else:
                # 说明是之前最低点收了阴线,最高点不算最低点那一天
                re_up_high_list = history_bars(stock, re_up_days - 1, '1d', 'high').tolist()
                re_up_highest = max(re_up_high_list)
                is_up_little = (re_up_highest / down_least) <= context.rebound_rate
            pass
        else:
            # 说了阳线,说明当天反弹了,需要最高值算上当天的幅度
            is_up_little = (re_up_highest / down_least) <= context.rebound_rate
            pass
    else:
        # 如果最高点不算最低点同一天
        # 反弹幅度 不超过1.11
        is_up_little = (re_up_highest / down_least) <= context.rebound_rate
    return is_up_little
